package com.niodata.dp.mybatis;

import com.niodata.dp.log.LogUtil;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.matchprocessor.ImplementingClassMatchProcessor;
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.apache.log4j.Logger;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

@Configuration
public class MyBatisConfiguration {

  private static final Logger logger = Logger.getLogger(MyBatisConfiguration.class);

  @Autowired
  ApplicationContext applicationContext;

  /**
   * create data source.
   *
   * @return DataSource
   */
  @Bean
  public DataSource createDataSource() throws Exception {
    HikariDataSourceFactory factory = new HikariDataSourceFactory();
    ResourceLoader loader = new DefaultResourceLoader();
    InputStream inputStream =
          loader.getResource("classpath:datasource.properties").getInputStream();
    Properties properties = new Properties();
    properties.load(inputStream);
    Set<Object> keys = properties.keySet();
    Properties dsproperties = new Properties();
    for (Object key : keys) {
      if (key.toString().startsWith("datasource")) {
        dsproperties.put(key.toString().replace("datasource.", ""), properties.get(key));
      }
    }
    factory.setProperties(dsproperties);
    inputStream.close();
    DataSource dataSource = factory.getDataSource();
    Connection connection = dataSource.getConnection();
    connection.createStatement().execute("select 1+1");
    connection.close();
    return dataSource;
  }

  /**
   * createSessionFactoryBean.
   *
   * @return SqlSessionFactoryBean sessionfactorybean
   * @throws Exception exception
   */
  @Bean
  public SqlSessionFactoryBean createSessionFactoryBean() throws Exception {
    SqlSessionFactoryBean factoryBean =
        new SqlSessionFactoryBean() {
        protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
        try {
          SqlSessionFactory factory = super.buildSqlSessionFactory();
          registerTypeHandler(factory);
          return factory;
        } catch (Exception e) {
          LogUtil.logStackTrace(logger, e);
          return null;
        }
      }
    };
    factoryBean.setDataSource(applicationContext.getBean(DataSource.class));
    ResourceLoader loader = new DefaultResourceLoader();
    String resource = "classpath:mybatis-config.xml";
    factoryBean.setConfigLocation(loader.getResource(resource));
    //factoryBean.setMapperLocations(this.getMapperResources());
    factoryBean.setSqlSessionFactoryBuilder(new SqlSessionFactoryBuilder());
    return factoryBean;
  }

  private void registerTypeHandler(SqlSessionFactory factory) throws Exception {
    TypeHandlerRegistry registry = factory.getConfiguration().getTypeHandlerRegistry();
    ResourceLoader loader = new DefaultResourceLoader();
    InputStream inputStream =
          loader.getResource("classpath:datasource.properties").getInputStream();
    Properties properties = new Properties();
    properties.load(inputStream);
    inputStream.close();
    String pkgs = properties.getProperty("mybatis.typehandler.packages");
    if (pkgs != null) {
      pkgs = "com.niodata.dp.mybatis;" + pkgs;
    } else {
      pkgs = "com.niodata.dp.mybatis";
    }
    String[] allPkgs = pkgs.split(";");
    for (String pkg : allPkgs) {
      pkg = pkg.trim();
      if (pkg.length() == 0) {
        continue;
      }
      FastClasspathScanner scanner = new FastClasspathScanner(pkg);
      scanner.matchClassesImplementing(
            DpMybatisTypeHandler.class,
            new ImplementingClassMatchProcessor() {
              @Override
              public void processMatch(Class implementingClass) {
              }
            });
      ScanResult result = scanner.scan();
      List<String> classes = result.getNamesOfClassesImplementing(DpMybatisTypeHandler.class);
      for (String clazz : classes) {
        Class classEntity = Class.forName(clazz);
        if (Modifier.isAbstract(classEntity.getModifiers())) {
          continue;
        }
        DpMybatisTypeHandler handler =
              (DpMybatisTypeHandler) (Class.forName(clazz).newInstance());
        registry.register(
              handler.getJavaClass(), handler.getJdbcType(), handler.getTypeHandlerClass());
      }
    }

  }

  private Resource[] getMapperResources() throws IOException {
    ResourceLoader loader = new DefaultResourceLoader();
    InputStream inputStream =
          loader.getResource("classpath:datasource.properties").getInputStream();
    Properties properties = new Properties();
    properties.load(inputStream);
    if (properties.get("mybatis.mapperLocations") == null) {
      return new Resource[0];
    }
    String[] patterns = properties.get("mybatis.mapperLocations").toString().split(";");
    List<Resource> resourceList = new ArrayList<Resource>();
    PathMatchingResourcePatternResolver resourcePatternResolver =
          new PathMatchingResourcePatternResolver();
    for (String pattern : patterns) {
      if (pattern.trim().length() == 0) {
        continue;
      }
      Resource[] resources = resourcePatternResolver.getResources(pattern);
      for (Resource res : resources) {
        resourceList.add(res);
      }
    }
    Resource[] resources = new Resource[resourceList.size()];
    return resourceList.toArray(resources);
  }
}
